package aconcagua.measure;

import static aconcagua.measure.UnitsTestResource.*
import static aconcagua.Aconcagua.INFINITY;
import static aconcagua.Aconcagua.MINUS_INFINITY;

import org.junit.Before;
import org.junit.Test;

class InfinityTest extends GroovyTestCase {

	private Measure oneKilometer;
	
	@Before
	public void setUp() throws Exception {
		
		super.setUp();
		
		oneKilometer = new Measure(1, KILOMETER);
	}
	
	// test accessing
	@Test
	void testAmount() {
		assert INFINITY == INFINITY;
	}
	
	@Test
	void testBaseUnit() {
		assert INFINITY.baseUnit() == new NullUnit();
	}
	
	@Test
	void testDenominator() {
		assert INFINITY.denominator() == 1;
	}
	
	@Test
	void testNumerator() {
		assert INFINITY.numerator() == INFINITY;
	}
	
	@Test
	void testUnit() {
		assert INFINITY.unit() == new NullUnit();
	}
	
	@Test
	void testUnitNameOf() {
		assert INFINITY.unitNameOf(METER) == 'meters';
	}
	
	// test add
	@Test
	void testAdd() {
		assert INFINITY + INFINITY == INFINITY;
		assert 9 + INFINITY == INFINITY;
		assert INFINITY + 9 == INFINITY;
		assert oneKilometer + INFINITY == INFINITY;
		assert INFINITY + oneKilometer == INFINITY;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY + MINUS_INFINITY });
		
		try {
			INFINITY + MINUS_INFINITY;
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Sum operation is indeterminate between Infinity and Minus Infinity";
			assert ex.operation() == "Sum";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == MINUS_INFINITY;
		};
		
		shouldFail(IndeterminateOperationException.class,
			{ MINUS_INFINITY + INFINITY });
		
		try {
			MINUS_INFINITY + INFINITY;
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Sum operation is indeterminate between Minus Infinity and Infinity";
			assert ex.operation() == "Sum";
			assert ex.leftOperand() == MINUS_INFINITY;
			assert ex.rightOperand() == INFINITY;
		};
	}
	
	@Test
	void testAddAssociativity() {
		assert (INFINITY + (INFINITY + oneKilometer)).amount() == ((INFINITY + INFINITY) + oneKilometer).amount();
		assert (INFINITY + (INFINITY + oneKilometer)).unit() == ((INFINITY + INFINITY) + oneKilometer).unit();
	}
	
	@Test
	void testAddCommutativity() {
		assert (INFINITY + oneKilometer).amount() == (oneKilometer + INFINITY).amount();
		assert (INFINITY + oneKilometer).unit() == (oneKilometer + INFINITY).unit();
	}
	
	@Test
	void testAddBigDecimalWithInfinity() {
		def number = 10.01g;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}
	
	@Test
	void testAddBigIntegerWithInfinity() {
		def number = 10g;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}

	@Test
	void testAddByteWithInfinity() {
		def number = 10.01 as byte;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}

	@Test
	void testAddDoubleWithInfinity() {
		def number = 10.01 as double;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}

	@Test
	void testAddFloatWithInfinity() {
		def number = 10.01 as float;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}

	@Test
	void testAddIntegerWithInfinity() {
		def number = 10.01 as int;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}

	@Test
	void testAddLongWithInfinity() {
		def number = 10.01 as long;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}

	@Test
	void testAddShortWithInfinity() {
		def number = 10.01 as short;

		assert number + INFINITY == INFINITY;
		assert INFINITY + number == INFINITY;
	}

	@Test
	void testSimplificationsAddingWithZero() {
		assert (INFINITY + 0) == INFINITY;
		assert (INFINITY + 0).unit() == INFINITY.unit();
	}
	
	// test subtract
	@Test
	void testSubtractAssociativity() {
		shouldFail(IndeterminateOperationException.class,
			{INFINITY - (INFINITY - oneKilometer) });
		
		try {
			INFINITY - (INFINITY - oneKilometer);
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Subtract operation is indeterminate between Infinity and Infinity";
			assert ex.operation() == "Subtract";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == INFINITY;
		};
	}

	@Test
	void testSubtractCommutativity() {
		assert (INFINITY - oneKilometer).amount() != (oneKilometer - INFINITY).amount();
		assert (INFINITY - oneKilometer).unit() == (oneKilometer - INFINITY).unit();
	}

	@Test
	void testSubtractBigDecimalWithInfinity() {
		def number = 10.01g;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSubtractBigIntegerWithInfinity() {
		def number = 10g;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSubtractByteWithInfinity() {
		def number = 10.01 as byte;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSubtractDoubleWithInfinity() {
		def number = 10.01 as double;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSubtractFloatWithInfinity() {
		def number = 10.01 as float;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSubtractIntegerWithInfinity() {
		def number = 10.01 as int;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSubtractLongWithInfinity() {
		def number = 10.01 as long;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSubtractShortWithInfinity() {
		def number = 10.01 as short;

		assert number - INFINITY == MINUS_INFINITY;
		assert INFINITY - number == INFINITY;
	}

	@Test
	void testSimplificationsSubtractingWithZero() {
		assert (INFINITY - 0) == INFINITY;
		assert (INFINITY - 0).unit() == INFINITY.unit();
	}
	
	// test multiplication
	@Test
	void testMultiplicationAssociativity() {
		assert (INFINITY * (INFINITY * oneKilometer)).amount() == ((INFINITY * INFINITY) * oneKilometer).amount();
		assert (INFINITY * (INFINITY * oneKilometer)).unit() == ((INFINITY * INFINITY) * oneKilometer).unit();
	}

	@Test
	void testMultiplicationCommutativity() {
		assert (INFINITY * oneKilometer).amount() == (oneKilometer * INFINITY).amount();
		assert (INFINITY * oneKilometer).unit() == (oneKilometer * INFINITY).unit();
	}

	@Test
	void testMultiplyBigDecimalWithInfinity() {
		def number = 10.01g;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testMultiplyBigIntegerWithInfinity() {
		def number = 10g;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testMultiplyByteWithInfinity() {
		def number = 10.01 as byte;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testMultiplyDoubleWithInfinity() {
		def number = 10.01 as double;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testMultiplyFloatWithInfinity() {
		def number = 10.01 as float;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testMultiplyIntegerWithInfinity() {
		def number = 10.01 as int;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testMultiplyLongWithInfinity() {
		def number = 10.01 as long;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testMultiplyShortWithInfinity() {
		def number = 10.01 as short;

		assert number * INFINITY == INFINITY;
		assert INFINITY * number == INFINITY;
	}

	@Test
	void testSimplificationsMultiplyingWithZero() {
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY * 0 });
		
		try {
			INFINITY * 0
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Multiplication operation is indeterminate between Infinity and 0";
			assert ex.operation() == "Multiplication";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == 0;
		};
	
		shouldFail(IndeterminateOperationException.class,
			{ 0 * INFINITY });
		
		try {
			0 * INFINITY
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Multiplication operation is indeterminate between Infinity and 0";
			assert ex.operation() == "Multiplication";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == 0;
		};
	}
	
	// test division
	@Test
	void testDivision() {
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / INFINITY });
		
		try {
			INFINITY / INFINITY
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and Infinity";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == INFINITY;
		};
	
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / MINUS_INFINITY });
		
		try {
			INFINITY / MINUS_INFINITY
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and Minus Infinity";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == MINUS_INFINITY;
		};
	
		shouldFail(IndeterminateOperationException.class,
			{ MINUS_INFINITY / INFINITY });
		
		try {
			MINUS_INFINITY / INFINITY
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Minus Infinity and Infinity";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == MINUS_INFINITY;
			assert ex.rightOperand() == INFINITY;
		};
		
		assert 9 / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / 9 });
		
		try {
			INFINITY / 9
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and 9";
			assert ex.operation == "Division";
			assert ex.leftOperand == INFINITY;
			assert ex.rightOperand == 9;
		};
	
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / oneKilometer });
			
		try {
			INFINITY / oneKilometer
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and 1 kilometer";
			assert ex.operation == "Division";
			assert ex.leftOperand == INFINITY;
			assert ex.rightOperand == oneKilometer;
		};
	}
	
	@Test
	void testDivideBigDecimalWithInfinity() {
		def number = 10.01g;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testDivideBigIntegerWithInfinity() {
		def number = 10g;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testDivideByteWithInfinity() {
		def number = 10.01 as byte;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testDivideDoubleWithInfinity() {
		def number = 10.01 as double;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testDivideFloatWithInfinity() {
		def number = 10.01 as float;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testDivideIntegerWithInfinity() {
		def number = 10.01 as int;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testDivideLongWithInfinity() {
		def number = 10.01 as long;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testDivideShortWithInfinity() {
		def number = 10.01 as short;

		assert number / INFINITY == 0;
		
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / number });
			
		try {
			INFINITY / number
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and ${number}";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == number;
		};
	}

	@Test
	void testSimplificationsDivideingWithZero() {
		shouldFail(IndeterminateOperationException.class,
			{ INFINITY / 0 });
			
		try {
			INFINITY / 0
		} catch (IndeterminateOperationException ex) {
			assert ex.getMessage() ==
				"The Division operation is indeterminate between Infinity and 0";
			assert ex.operation() == "Division";
			assert ex.leftOperand() == INFINITY;
			assert ex.rightOperand() == 0;
		};
	}
	
	// test bag creation
	@Test
	void testBagCreationWithMeasure() {
		MeasureBag bag = (10 * METER) + (INFINITY * SECOND);

		assert bag.isMeasureBag();
		assert bag.measures().contains(10 * METER);
		assert bag.measures().contains(INFINITY * SECOND);
	}
	
	@Test
	void testBagCreationWithNumber() {
		MeasureBag bag = 10 + (INFINITY * SECOND);
		MeasureBag otherBag = (INFINITY * METER) + 10;

		assert bag.isMeasureBag();
		assert bag.measures().contains(10);
		assert bag.measures().contains(INFINITY * SECOND);

		assert otherBag.isMeasureBag();
		assert otherBag.measures().contains(10);
		assert otherBag.measures().contains(INFINITY * METER);
	}
	
	// test comparing
	@Test
	void testEqualsMeasure() {
		Measure measure = PESO.with(5);
		
		assert INFINITY.equalsMeasure(INFINITY);
		assert !INFINITY.equalsMeasure(measure);
	}
	
	@Test
	void testEqualsMeasureBag() {
		MeasureBag measureBag = PESO.with(5) + DOLLAR.with(5);
		
		assert INFINITY.equalsMeasureBag(INFINITY);
		assert !INFINITY.equalsMeasureBag(measureBag);
	}
	
	// test converting
	@Test
	void testConvertAmountToBaseUnit() {
		assert INFINITY.convertAmountToBaseUnit() == INFINITY;
	}
	
	@Test
	void testConvertTo() {
		shouldFail(CanNotConvertMeasureException.class ,
			{ INFINITY.convertTo(PESO) });
		try {
			INFINITY.convertTo(PESO)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.getMessage() == "It is not possible to convert Infinity to peso";
			assert ex.sourceMeasure() == INFINITY;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testConvertToBaseUnit() {
		assert INFINITY.convertToBaseUnit() == INFINITY;
	}
	
	// test interval protocol
	@Test
	void testToBy() {
		ArithmeticObjectInterval interval = INFINITY.to(INFINITY, 5);
		assert interval.getFrom() == INFINITY;
		assert interval.getTo() == INFINITY;
		assert interval.size() == INFINITY;
		assert interval.step() == 5;
	}
}
